package com.hqd.ch03.v49.boot.context.config;

import com.hqd.ch03.utils.ResourceUtils;
import com.hqd.ch03.v49.io.Resource;
import com.hqd.ch03.v49.io.ResourceLoader;
import com.hqd.ch03.v49.io.support.FileSystemResource;
import com.hqd.ch03.v49.utils.StringUtils;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

class LocationResourceLoader {

    private static final Resource[] EMPTY_RESOURCES = {};

    private static final Comparator<File> FILE_PATH_COMPARATOR = Comparator.comparing(File::getAbsolutePath);

    private static final Comparator<File> FILE_NAME_COMPARATOR = Comparator.comparing(File::getName);

    private final ResourceLoader resourceLoader;

    /**
     * Create a new {@link LocationResourceLoader} instance.
     *
     * @param resourceLoader the underlying resource loader
     */
    LocationResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /**
     * Returns if the location contains a pattern.
     *
     * @param location the location to check
     * @return if the location is a pattern
     */
    boolean isPattern(String location) {
        return StringUtils.hasLength(location) && location.contains("*");
    }

    /**
     * Get a single resource from a non-pattern location.
     *
     * @param location the location
     * @return the resource
     * @see #isPattern(String)
     */
    Resource getResource(String location) {
        validateNonPattern(location);
        location = StringUtils.cleanPath(location);
        if (!ResourceUtils.isUrl(location)) {
            location = ResourceUtils.FILE_URL_PREFIX + location;
        }
        return this.resourceLoader.getResource(location);
    }

    private void validateNonPattern(String location) {
    }

    /**
     * Get a multiple resources from a location pattern.
     *
     * @param location the location pattern
     * @param type     the type of resource to return
     * @return the resources
     * @see #isPattern(String)
     */
    Resource[] getResources(String location, ResourceType type) {
        validatePattern(location, type);
        String directoryPath = location.substring(0, location.indexOf("*/"));
        String fileName = location.substring(location.lastIndexOf("/") + 1);
        Resource resource = getResource(directoryPath);
        if (!resource.exists()) {
            return EMPTY_RESOURCES;
        }
        File file = getFile(location, resource);
        if (!file.isDirectory()) {
            return EMPTY_RESOURCES;
        }
        File[] subDirectories = file.listFiles(this::isVisibleDirectory);
        if (subDirectories == null) {
            return EMPTY_RESOURCES;
        }
        Arrays.sort(subDirectories, FILE_PATH_COMPARATOR);
        if (type == ResourceType.DIRECTORY) {
            return Arrays.stream(subDirectories).map(FileSystemResource::new).toArray(Resource[]::new);
        }
        List<Resource> resources = new ArrayList<>();
        FilenameFilter filter = (dir, name) -> name.equals(fileName);
        for (File subDirectory : subDirectories) {
            File[] files = subDirectory.listFiles(filter);
            if (files != null) {
                Arrays.sort(files, FILE_NAME_COMPARATOR);
                Arrays.stream(files).map(FileSystemResource::new).forEach(resources::add);
            }
        }
        return resources.toArray(EMPTY_RESOURCES);
    }

    private void validatePattern(String location, ResourceType type) {
    }

    private File getFile(String patternLocation, Resource resource) {
        try {
            return resource.getFile();
        } catch (Exception ex) {
            throw new IllegalStateException(
                    "Unable to load config data resource from pattern '" + patternLocation + "'", ex);
        }
    }

    private boolean isVisibleDirectory(File file) {
        return file.isDirectory() && !file.getName().startsWith("..");
    }

    /**
     * Resource types that can be returned.
     */
    enum ResourceType {

        /**
         * Return file resources.
         */
        FILE,

        /**
         * Return directory resources.
         */
        DIRECTORY

    }

}

